﻿using System.Data.Entity;
using App.Core.Domain;
using System.Data.Entity.Infrastructure;
using System;
using System.ComponentModel;
using App.Core.Domain.Auditing;
using System.Linq;
using System.Threading.Tasks;
using App.Core.Common;
using App.Core.Domain.BaseObject;
using EntityFramework.DynamicFilters;

namespace App.Core.DataAccess
{

    public class Db : DbContext
    {
        public Db()
            : base("WebPlus")
        {
            Configuration.LazyLoadingEnabled = false;

            ((IObjectContextAdapter)this)
                .ObjectContext
                .ObjectStateManager
                .ObjectStateManagerChanged += ObjectStateManager_ObjectStateManagerChanged;
        }

        public DbSet<User> User { get; set; }
        public DbSet<Role> Role { get; set; }
        public DbSet<Team> Team { get; set; }
        public DbSet<MenuPermission> MenuPermission { get; set; }
        public DbSet<Permission> Permission { get; set; }
        public DbSet<RolePermission> RolePermission { get; set; }
        public DbSet<UserPermission> UserPermission { get; set; }
        public DbSet<Button> Button { get; set; }
        public DbSet<LoginLog> LoginLog { get; set; }
        public DbSet<Setting> Setting { get; set; }
        public DbSet<CalendarEvent> CalendarEvent { get; set; }
        public DbSet<Dictionary> Dictionary { get; set; }

        public DbSet<GoodsBrand> GoodsBrand { get; set; }
        public DbSet<GoodsCategory> GoodsCategory { get; set; }
        public DbSet<Goods> Goods { get; set; }
        public DbSet<GoodsPackage> GoodsPackage { get; set; }
        public DbSet<RedeemCode> RedeemCode { get; set; }
        public DbSet<Order> Order { get; set; }
        public DbSet<Address> Address { get; set; }

        public virtual IDbSet<T> DbSet<T>() where T : class
        {
            return Set<T>();
        }

        public override int SaveChanges()
        {
            ApplyConcepts();
            return base.SaveChanges();
        }

        public override async Task<int> SaveChangesAsync()
        {
            ApplyConcepts();
            return await base.SaveChangesAsync();
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Filter("SoftDelete", (ISoftDelete d) => d.IsDeleted, false);

            modelBuilder.Entity<GoodsBrand>().HasOptional(s => s.Seo).WithMany();
            modelBuilder.Entity<GoodsCategory>().HasOptional(s => s.Seo).WithMany();
            modelBuilder.Entity<Goods>().HasOptional(s => s.Seo).WithMany();

            Database.SetInitializer<Db>(null);

        }

        private void ObjectStateManager_ObjectStateManagerChanged(object sender, CollectionChangeEventArgs e)
        {
            var contextAdapter = (IObjectContextAdapter)this;
            if (e.Action != CollectionChangeAction.Add)
            {
                return;
            }

            var entry = contextAdapter.ObjectContext.ObjectStateManager.GetObjectStateEntry(e.Element);
            switch (entry.State)
            {
                case EntityState.Added:
                    CheckAndSetId(entry.Entity);
                    SetCreationAuditProperties(entry.Entity, CurrentSession.GetInstance().UserId);
                    break;
            }
        }

        protected virtual void CheckAndSetId(object entityAsObj)
        {
            //Set GUID Ids
            var entity = entityAsObj as IEntity<Guid>;
            if (entity != null && entity.Id == Guid.Empty)
            {
                entity.Id = SequentialGuidGenerator.Instance.Create();
            }
        }

        protected virtual void SetModificationAuditProperties(DbEntityEntry entry, Guid? userId)
        {
            if (entry.Entity is IHasModificationTime)
            {
                entry.Cast<IHasModificationTime>().Entity.LastModificationTime =DateTime.Now;
            }

            if (entry.Entity is IModificationAudited)
            {
                var entity = entry.Cast<IModificationAudited>().Entity;

                if (userId == null)
                {
                    entity.LastModifierUserId = null;
                    return;
                }

                entity.LastModifierUserId = userId;
            }
        }


        private void ApplyConcepts()
        {
            var userId = CurrentSession.GetInstance().UserId;
            var entries = ChangeTracker.Entries().ToList();
            foreach (var entry in entries)
            {
                switch (entry.State)
                {
                    case EntityState.Modified:
                        SetModificationAuditProperties(entry, userId);
                        break;
                    case EntityState.Deleted:
                        CancelDeletionForSoftDelete(entry);
                        SetDeletionAuditProperties(entry.Entity, userId);
                        break;
                }
            }
        }

        protected virtual void CancelDeletionForSoftDelete(DbEntityEntry entry)
        {
            if (!(entry.Entity is ISoftDelete))
            {
                return;
            }

            var softDeleteEntry = entry.Cast<ISoftDelete>();

            softDeleteEntry.State = EntityState.Unchanged; //TODO: Or Modified? IsDeleted = true makes it modified?
            softDeleteEntry.Entity.IsDeleted = true;
        }

        protected virtual void SetDeletionAuditProperties(object entityAsObj, Guid? userId)
        {
            if (entityAsObj is IDeletionAudited)
            {
                var entity = entityAsObj.As<IDeletionAudited>();

                if (entity.DeletionTime == null)
                {
                    entity.DeletionTime = DateTime.Now;
                }

                if (entity.DeleterUserId != null)
                {
                    return;
                }

                if (userId == null)
                {
                    entity.DeleterUserId = null;
                    return;
                }

                entity.DeleterUserId = userId;
            }
        }

        protected virtual void SetCreationAuditProperties(object entityAsObj, Guid? userId)
        {
            if (entityAsObj is IHasCreationTime)
            {
                entityAsObj.As<IHasCreationTime>().CreationTime = DateTime.Now;
            }

            if (userId.HasValue && entityAsObj is ICreationAudited)
            {
                var entity = entityAsObj.As<ICreationAudited>();
                if (entity.CreatorUserId == null)
                {
                    entity.CreatorUserId = userId;
                }
            }
        }
    }

    internal class Initialiser : DropCreateDatabaseAlways<Db>
    {
        protected override void Seed(Db context)
        {
            context.SaveChanges();
        }
    }

}